package cn.kk.fluidcells.cells.mouse.internal;

import java.util.LinkedList;
import java.util.List;

import cn.kk.fluidcells.cells.keyboard.internal.MediaCodeSupport;
import cn.kk.fluidcells.cells.mouse.MouseListener;

import com.sun.jna.platform.win32.Kernel32;
import com.sun.jna.platform.win32.User32;
import com.sun.jna.platform.win32.WinDef.HMODULE;
import com.sun.jna.platform.win32.WinDef.WPARAM;
import com.sun.jna.platform.win32.WinUser;
import com.sun.jna.platform.win32.WinUser.HHOOK;
import com.sun.jna.platform.win32.WinUser.MSG;

public class MouseHook implements MediaCodeSupport {
    User32 lib;

    static HHOOK hhk;

    private List<MouseListener> listeners;

    private HMODULE hMod;

    private boolean started = false;

    private DefaultLowLevelMouseProc hookProc;

    public MouseHook() {
        this.lib = User32.INSTANCE;
        this.hMod = Kernel32.INSTANCE.GetModuleHandle(null);
        this.listeners = new LinkedList<MouseListener>();
    }

    public boolean addMouseListener(MouseListener listener) {
        return this.listeners.add(listener);
    }

    public boolean removeMouseListener(MouseListener listener) {
        return this.listeners.remove(listener);
    }

    public void init() {
        this.hookProc = new DefaultLowLevelMouseProc(this);
    }

    public void start() {
        if (!started) {
            this.started = true;
            hhk = this.lib.SetWindowsHookEx(WinUser.WH_MOUSE_LL, this.hookProc, this.hMod, 0);
            if (hhk != null) {
                System.out.println("mouse: hooked");
                new Thread() {
                    public void run() {
                        MSG msg = new MSG();
                        lib.GetMessage(msg, null, 0, 0);
                    }
                }.start();
            } else {
                System.err.println("mouse: hook failed");
                throw new RuntimeException("mouse: hook failed");
            }
        }
    }

    public void stop() {
        if (this.started) {
            this.lib.UnhookWindowsHookEx(MouseHook.hhk);
            this.started = false;
        }
    }

    public static void main(String[] args) throws Exception {
        MouseHook mouseHook = new MouseHook();
        mouseHook.init();
        mouseHook.start();
        Thread.sleep(10000);
        mouseHook.stop();
    }

    public void on(MouseAction action, int nCode, WPARAM wParam, MouseHookStruct lParam) {
        for (MouseListener l : listeners) {
            switch (action) {
            case LEFT_BTN_DOWN:
                l.lButtonDown(nCode, wParam, lParam);
                break;
            case LEFT_BTN_UP:
                l.lButtonUp(nCode, wParam, lParam);
                break;
            case MIDDLE_BTN_DOWN:
                l.mButtonDown(nCode, wParam, lParam);
                break;
            case MIDDLE_BTN_UP:
                l.mButtonUp(nCode, wParam, lParam);
                break;
            case MOVE:
                l.mouseMove(nCode, wParam, lParam);
                break;
            case NC_BTN_DOWN:
                l.ncButtonDown(nCode, wParam, lParam);
                break;
            case NC_BTN_UP:
                l.ncButtonUp(nCode, wParam, lParam);
                break;
            case NC_MOVE:
                l.ncMouseMove(nCode, wParam, lParam);
                break;
            case RIGHT_BTN_DOWN:
                l.rButtonDown(nCode, wParam, lParam);
                break;
            case RIGHT_BTN_UP:
                l.rButtonUp(nCode, wParam, lParam);
                break;
            case WHEEL:
                l.mouseWheel(nCode, wParam, lParam);
                break;
            case X_BTN_DOWN:
                l.xButtonDown(nCode, wParam, lParam);
                break;
            case X_BTN_UP:
                l.xButtonUp(nCode, wParam, lParam);
                break;
            }
        }
    }

    /*
     * private MouseWheelEvent createMouseWheelEvent(int eventType, MouseHookStruct lParam) { POINT pt = lParam.pt; //
     * FIXME race condition where relativeTo might not be visible Point rl = relativeTo.getLocationOnScreen(); int x =
     * pt.x - rl.x; int y = pt.y - rl.y; int wheelRotation = lParam.mouseData.intValue() >> 16; return new
     * MouseWheelEvent(relativeTo, eventType, lParam.time.longValue(), 0, x, y, 0, false,
     * MouseWheelEvent.WHEEL_UNIT_SCROLL, 1, wheelRotation * -1); }
     * 
     * 
     * private MouseEvent createMouseEvent(int eventType, int button, MouseHookStruct lParam) { POINT pt = lParam.pt; //
     * FIXME race condition where relativeTo might not be visible Point rl = relativeTo.getLocationOnScreen(); int x =
     * pt.x - rl.x; int y = pt.y - rl.y; return new MouseEvent(relativeTo, eventType, lParam.time.longValue(), 0, x, y,
     * 0, false, button); }
     */

}
